#!/usr/bin/perl -w

use strict;

my (%OPS, %MAP_FUNC_TO_OP, %MAP_OP_TO_SEQSET, %DOC, $map);
my (%GENERIC_OP_TO_FUNC);

my $xml = shift @ARGV;

open F, "cat @ARGV |" or die "OPS*: $!";
while (<F>) {
  next if ! /^OP_/;
  /(\w+)\s+N_\("(.+)"\)/ or die "$.: parse error";
  $OPS{$1} = $2;
}
close F;

while (<STDIN>) {
  if (/^const struct menu_func_op_t Op.*{ \/\* map: (.*) \*\//) {
    $map = $1;
    $DOC{$map} = "";
    $MAP_FUNC_TO_OP{$map} = {};
    $MAP_OP_TO_SEQSET{$map} = {};
    while (<STDIN>) {
      last if (/^}/);
      if (/^\s*\*\*\s*(.*)/) {
        $DOC{$map} .= "$1\n";
      }
      elsif (/{\s*"(.+)"\s*,\s*(\w+)\s*}/) {
        my ($function, $op) = ($1, $2);
        die "unknown OP $op" unless $OPS{$op};
        $MAP_FUNC_TO_OP{$map}->{$function} = $op;
        $MAP_OP_TO_SEQSET{$map}->{$op} = {};
        if ($map eq "generic") {
          $GENERIC_OP_TO_FUNC{$op} = $function;
        }
      }
    }
  }
  elsif (/^const struct menu_op_seq_t .*{ \/\* map: (.*) \*\//) {
    $map = $1;
    die "unknown map $map" unless $MAP_FUNC_TO_OP{$map};
    while (<STDIN>) {
      last if (/^}/);
      if (/{\s*(\w+)\s*,\s*"(.+)"\s*}/) {
        my ($op, $binding) = ($1, $2);

        # If the $op is NOT in the current map, but it IS
        # in the generic map, then we need to add a stub entry
        # in $MAP_FUNC_TO_OP and $MAP_OP_TO_SEQSET for this case.
        unless ($MAP_OP_TO_SEQSET{$map}->{$op}) {
          if (($map eq "pager") or ($map eq "editor")) {
            die "unknown OP $op for map $map";
          }

          my $function = $GENERIC_OP_TO_FUNC{$op};
          unless ($function) {
            die "unknown OP $op for map $map";
          }

          $MAP_FUNC_TO_OP{$map}->{$function} = $op;
          $MAP_OP_TO_SEQSET{$map}->{$op} = {};
        }

        $binding =~ s/&/&amp;/;
        # for <key>, try CamelCasing into <Key>
        $binding =~ s/<(.)(.+)>/&lt;\U$1\E$2&gt;/;
        $binding =~ s/</&lt;/;
        $binding =~ s/>/&gt;/;
        $binding =~ s/ /&lt;Space&gt;/;
        $binding =~ s/^\\033/Esc /;
        $binding =~ s/^\\010/&lt;Backspace&gt;/;
        $binding =~ s/^\\177/&lt;Delete&gt;/;
        $binding =~ s/^\\(0\d+)$/'^'.chr(64+oct($1))/e;
        $binding =~ s/^\\(0\d+)(.)/'^'.chr(64+oct($1)) ." $2"/e;
        $binding =~ s/\\t/&lt;Tab&gt;/;
        $binding =~ s/\\r/&lt;Return&gt;/;
        $binding =~ s/\\n/&lt;Enter&gt;/;
        die "unknown key $binding" if $binding =~ /\\[^\\]|<|>/;
        $MAP_OP_TO_SEQSET{$map}->{$op}->{$binding} = 1;
      }
    }
  }
}

open XML, $xml or die "$xml: $!";
while (<XML>) {
  if (/__print_map\((.*)\)/) {
    my $map = $1;
    my $mapid = $1;
    my $maptitle = $1;
    $mapid =~ s/\s+/-/g;
    $maptitle =~ s/\b(\w)/\u$1/g;
    unless ($MAP_FUNC_TO_OP{$map}) {
      warn "map $map undefined";
      next;
    }
    print <<EOT;
<sect2 id="${mapid}-map">
<title>$maptitle Menu</title>
$DOC{$map}

<table id="tab-${mapid}-bindings">
<title>Default $maptitle Menu Bindings</title>
<tgroup cols="3">
<thead>
<row><entry>Function</entry><entry>Default key</entry><entry>Description</entry></row>
</thead>
<tbody>
EOT

    # Sort the map entries by function, then by binding
    foreach my $function (sort keys %{$MAP_FUNC_TO_OP{$map}}) {
      my $op = $MAP_FUNC_TO_OP{$map}->{$function};
      my $bindings = $MAP_OP_TO_SEQSET{$map}->{$op};

      if (scalar keys %{$bindings}) {
        foreach my $binding (sort keys %{$bindings}) {
          print "<row><entry><literal>&lt;" .
            $function .
            "&gt;</literal></entry><entry>" .
            $binding .
            "</entry><entry>" .
            $OPS{$op} .
            "</entry></row>\n";
        }
      }
      else {
        print "<row><entry><literal>&lt;" .
          $function .
          "&gt;</literal></entry><entry>" .
          "</entry><entry>" .
            $OPS{$op} .
          "</entry></row>\n";
      }
    }

    print <<EOT;
</tbody>
</tgroup>
</table>

</sect2>

EOT
    delete $MAP_FUNC_TO_OP{$map};
  } else {
    print;
  }
}
close XML;

warn "unprinted maps: ". join(" ", keys %MAP_FUNC_TO_OP) if %MAP_FUNC_TO_OP;
